package chat_server

import (
	"bufio"
	"io"
	"net"
	"sync/atomic"
	"time"
)

type tChatClient struct {
	conn net.Conn
	name string
	openFlag int32
	closeFlag int32
	serverFlag bool

	closeChan chan bool
	sendChan chan IMsg

	sendLogs []IMsg
	dropLogs []IMsg
	recvLogs []IMsg
	pendingSend int32
	timeoutCounter int

	recvHandler ClientRecvFunc
	closeHandler ClientCloseFunc
}

var gMaxPendingSend int32 = 100

func DialChatClient(address string) (error, IChatClient) {
	conn, err := net.Dial("tcp", address)
	if err != nil {
		return err, nil
	}

	return nil, openChatClient(conn, false)
}

func openChatClient(conn net.Conn, serverFlag bool) IChatClient {
	it := &tChatClient{
		conn: conn,
		openFlag: 0,
		closeFlag: 0,
		serverFlag: serverFlag,

		closeChan: make(chan bool, 3),
		sendChan: make(chan IMsg, gMaxPendingSend),

		name: "anonymous",
		sendLogs: []IMsg{},
		dropLogs: []IMsg{},
		recvLogs: []IMsg{},
		pendingSend: 0,
		timeoutCounter: 0,
	}
	it.open()
	return it
}


func (me *tChatClient) GetName() string {
	return me.name
}

func (me *tChatClient) SetName(name string) {
	me.name = name
}

func (me *tChatClient) open(){
	if !atomic.CompareAndSwapInt32(&me.openFlag, 0, 1) {
		return
	}

	go me.beginWrite()
	go me.beginRead()
	go me.beginWatchRecvTimeout()
}


func (me *tChatClient) isClosed() bool {
	return me.closeFlag != 0
}

func (me *tChatClient) isNotClosed() bool {
	return !me.isClosed()
}

func (me *tChatClient) Send(msg IMsg) {
	if me.isClosed() {
		return
	}

	if me.pendingSend < gMaxPendingSend {
		atomic.AddInt32(&me.pendingSend, 1)
		me.sendChan <- msg

	} else {
		me.dropLogs = append(me.dropLogs, msg)
	}
}

func (me *tChatClient) RecvHandler(handler ClientRecvFunc) {
	if me.isNotClosed() {
		me.recvHandler = handler
	}
}


func (me *tChatClient) CloseHandler(handler ClientCloseFunc) {
	if me.isNotClosed() {
		me.closeHandler = handler
	}
}


func (me *tChatClient) Close() {
	if me.isNotClosed() {
		me.closeConn()
	}
}

func (me *tChatClient) beginWrite() {
	Logging.Logf("tChatClient.beginWrite, %v, serverFlag=%v", me.name, me.serverFlag)
	writer := io.Writer(me.conn)
	for {
		select {
		case <- me.closeChan:
			Logging.Logf("tChatClient.beginWrite, <- closeChan, %v, serverFlag=%v", me.name, me.serverFlag)
			_ = me.conn.Close()
			me.closeFlag = 2
			me.postConnClosed()
			return

		case msg := <- me.sendChan:
			atomic.AddInt32(&me.pendingSend, -1)
			_,e := writer.Write([]byte(msg.Encode()))
			if e != nil {
				Logging.Logf("tChatClient.beginWrite, write error, %v, serverFlag=%v", me.name, me.serverFlag)
				me.closeConn()

			} else {
				me.sendLogs = append(me.sendLogs, msg)
			}
			break

		//case <- time.After(time.Duration(5) * time.Second):
		//	me.postRecvTimeout()
		//	break
		}
	}
}

func (me *tChatClient) postRecvTimeout() {
	Logging.Logf("tChatClient.postRecvTimeout, %v, serverFlag=%v", me.name, me.serverFlag)
	me.closeConn()
}

func (me *tChatClient) beginRead() {
	reader := bufio.NewReader(me.conn)
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			Logging.Logf("tChatClient.beginRead, read error, %v, serverFlag=%v", me.name, me.serverFlag)
			me.closeConn()
			break
		}

		// reset timeout counter
		me.timeoutCounter = 0

		ok, msg := MsgDecoder.Decode(line)
		if ok {
			fn := me.recvHandler
			if fn != nil {
				fn(me, msg)
			}

			me.recvLogs = append(me.recvLogs, msg)
		}
	}
}


func (me *tChatClient) beginWatchRecvTimeout() {
	duration := time.Duration(5)
	for range time.Tick(duration * time.Second) {
		if me.isClosed() {
			break
		}

		me.timeoutCounter++
		if me.timeoutCounter >= 3 {
			me.postRecvTimeout()
		}
	}
}

func (me *tChatClient) closeConn() {
	if !atomic.CompareAndSwapInt32(&me.closeFlag, 0, 1) {
		return
	}

	Logging.Logf("tChatClient.closeConn, %v, serverFlag=%v", me.name, me.serverFlag)
	me.closeChan <- true
}

func (me *tChatClient) postConnClosed() {
	Logging.Logf("tChatClient.postConnClosed, %v, serverFlag=%v", me.name, me.serverFlag)

	handler := me.closeHandler
	if handler != nil {
		handler(me)
	}

	me.closeHandler = nil
	me.recvHandler = nil
}